﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Reflection;
using System.Configuration;

using Db4objects.Db4o;
using Db4objects.Db4o.Query;


using Db4o.Framework.Interfaces;
using Db4o.Framework.Exceptions;
using Db4o.Framework.Query;

namespace Db4o.Framework
{
    /// <summary>
    /// This class is used to query on object and manage database instance.
    /// </summary>
    public class DataManager : ILogged
    {
        #region Design pattern : << Singleton DataManager >>

	    // read-only private member
        private static readonly DataManager instance = new DataManager();
        
	        // private construtor
        private DataManager()
        {
        }

	        // public property
        public static DataManager Instance { get { return instance; } }

        #endregion

        private IObjectContainer database;
        private string fileName;
 

        #region Properties

        /// <summary>
        /// Object that contain the database 
        /// </summary>
        internal IObjectContainer Database
        {
            get { return database; }
            set { database = value; }
        }
        public string FileName
        {
            get { return fileName; }
        }
        #endregion

        #region Managing DataBase
        /// <summary>
        /// Open or create database file
        /// If exists the file will be deleted
        /// </summary>
        public void OpenDatabase(string file)
        {
            OpenDatabase(file, false);
        }
        /// <summary>
        /// Open or create database file
        /// </summary>
        /// <param name="delete">if true the file will be deleted</param>
        public void OpenDatabase(string file, bool delete)
        {
            if (System.IO.File.Exists(file) && delete == true)
            {
                logger.InfoFormat("delete database file [{0}]", file);
                System.IO.File.Delete(file);
            }

            try
            {
                logger.InfoFormat("open database file [{0}]", file);
                Database = Db4oFactory.OpenFile(file);
                fileName = file;
            }
            catch (Exception e)
            {
                ExceptionManager.Instance.ThrowFatalLogException("CoreInitializationException", "Error during database instanciation",e);
                throw;
            }
        }

        public void OpenRemoteConnexion(string host, int port, string user, string password)
        {
            try
            {
                if (Database == null)
                {
                    Database = Db4oFactory.OpenClient(host, port, user, password);
                }
            }
            catch (Exception e)
            {
                ExceptionManager.Instance.ThrowFatalLogException("RemoteConnexionException", "Can't reach db4o server", e);
                throw;
            }
        }
        /// <summary>
        /// Close the database
        /// </summary>
        public void Close()
        {
            Close(false);
        }
        /// <summary>
        /// Close and delete the database
        /// </summary>
        /// <param name="delete">true/false delete the database</param>
        public void Close(bool delete)
        {
            try
            {
                if (Database != null)
                {
                    logger.InfoFormat("close database {0}", delete ? " - [delete " + FileName + "]" : "");
                    Database.Close();
                    if (delete)
                        System.IO.File.Delete(FileName);
                }
            }
            catch(Db4objects.Db4o.Ext.Db4oException e)
            {
                ExceptionManager.Instance.ThrowFatalLogException("DatabaseException", "Error while closing database", e);
            }
        }

        /// <summary>
        /// Rollback transaction and refresh DbEntity objects in parameters
        /// </summary>
        /// <param name="obj"></param>
        public void RollBack(params ManagedType[] obj)
        {
            
            Database.Ext().Rollback();
            foreach (ManagedType o in obj)
            {
                logger.InfoFormat("[rollback] database object: {0}",o.ToString());
                Database.Ext().Refresh(o, int.MaxValue);
            }
        }
        /// <summary>
        /// Commit the transaction
        /// </summary>
        public void Commit()
        {
            logger.InfoFormat("[commit] database changes");
            Database.Ext().Commit();
        }
        /// <summary>
        /// Return object Id
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>(
        public long GetObjectId(ManagedType obj)
        {
            return Database.Ext().GetID(obj);
        }
        #endregion

        /// <summary>
        /// Delete object and commit changes
        /// </summary>
        /// <param name="obj">object to delete</param>
        public void Delete(ManagedType obj)
        {
            Delete(obj, true);
        }
        /// <summary>
        /// Delete object 
        /// </summary>
        /// <param name="obj">object to delete</param>
        /// <param name="commit">commit changes</param>
        public void Delete(ManagedType obj,bool commit)
        {
            if (obj != null)
            {
                logger.InfoFormat("[delete] managed object ", obj.ToString());
                Database.Delete(obj);
                if (commit)
                    Commit();
            }
        }

        /// <summary>
        /// Return the DbEntity specified by the id 
        /// </summary>
        /// <param name="Type">Type of DbEntity</param>
        /// <param name="value">unique object uuid </param>
        /// <returns></returns>
        public T RetreiveById<T>(long value) where T : ManagedType
        {
            return QueryFactory.Instance.RetreiveById<T>(value);
        }
        /// <summary>
        /// Retreive all stored object items of specified generic type 
        /// </summary>
        /// <typeparam name="T">Managed type you want in return</typeparam>
        /// <returns></returns>
        public IList<T> Retreive<T>() where T : ManagedType
        {
            return QueryFactory.Instance.Retreive<T>();
        }
        /// <summary>
        /// Retreive all objects of specified type wich match the value
        /// </summary>
        /// <param name="type">generic type</param>
        /// <param name="property">property to test</param>
        /// <param name="value">specified value</param>
        /// <typeparam name="T">list type</typeparam>
        /// <returns>list of specified generic type</returns>
        public IList<T> RetreiveByValue<T>(string property, object value) where T : ManagedType
        {
            return QueryFactory.Instance.RetreiveByValue<T>(property, value);
        }

        /// <summary>
        /// Retreive all objects of specified type wich match all the values
        /// </summary>
        /// <typeparam name="T">generic type</typeparam>
        /// <param name="values">properties and values hastable</param>
        /// <returns>list of specified generic type</returns>
        public IList<T> RetreiveByValue<T>(Hashtable values) where T : ManagedType
        {
            return QueryFactory.Instance.RetreiveByValue<T>(values);
        }

        /// <summary>
        /// Retreive all objects of specified type wich match all the values
        /// </summary>
        /// <typeparam name="T">generic type</typeparam>
        /// <param name="args">properties and values object table</param>
        /// <returns></returns>
        public IList<T> RetreiveByValue<T>(params object[] args) where T : ManagedType
        {
            Hashtable param = new Hashtable();
            int i = 0;
            while(i<args.Length)
            {
                param.Add(args[i], args[++i]);
                i++;
            }
            
            return QueryFactory.Instance.RetreiveByValue<T>(param);
        }

        /// <summary>
        /// Execute BusinessFactory.Query() method
        /// </summary>
        /// <typeparam name="T">ManagedType type</typeparam>
        /// <param name="e">delegate that represent native query</param>
        /// <returns>list of T</returns>
        public IList<T> Query<T>(System.Predicate<T> e) where T : ManagedType
        {
            return QueryFactory.Instance.NativeQuery<T>(e);
        }
        /// <summary>
        /// Store the specified object in database
        /// </summary>
        /// <param name="obj">object to store</param>
        /// <returns></returns>
        public long Store(ManagedType obj)
        {
            try
            {

                if (obj.Id == 0)
                {
                    // store to get an id
                    DataManager.Instance.Database.Set(obj);
                    logger.DebugFormat("[requestID] - {0}: {1}", obj.GetType().ToString(), obj.ToString());
                    obj.SetId(DataManager.Instance.GetObjectId(obj));
                    // store with unique id
                    logger.InfoFormat("[storedID] - {0}: {1}", obj.GetType().ToString(), obj.ToString());
                    DataManager.Instance.Database.Set(obj);
                }
                else
                {
                    // store 
                    DataManager.Instance.Database.Set(obj);
                    logger.InfoFormat("[stored] - {0}: {1}", obj.GetType().ToString(), obj.ToString());
                }

                return obj.Id;
            }
            catch (Exception e)
            {
                ExceptionManager.Instance.ThrowWarnLogException("DatabaseException", "Error while storing object " + obj.ToString());
                throw e;
            }
        }
        /// <summary>
        /// Check if the soecified type is managed 
        /// </summary>
        /// <param name="type">type to test</param>
        /// <returns>true/false</returns>
        //private static bool CheckManagedEntity(Type type)
        //{
        //    if (type.IsSubclassOf(typeof(ManagedType)))
        //        return true;
        //    else
        //    {
        //        ExceptionManager.Instance.ThrowWarnLogException("DatabaseException", "Object is not managed, verify declaration");
        //        return false;
        //    }
        //}
    }
}
